package com.nulldev.util.net.tcp;

import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;

public abstract class fRetry {
	private final Logger logger = LoggerFactory.getLogger();
	protected final String category, name;
	protected volatile boolean running;
	final Object runLock = new Object();
	volatile Thread retryThread;
	int delayIndex;
	int[] retryDelays = new int[] {1 * 1000, 3 * 1000, 5 * 1000, 8 * 1000, 13 * 1000};

	public fRetry (String category, String name) {
		this.category = category;
		this.name = name;
	}

	/** Starts a thread which calls {@link #initialize()} and then repeatedly calls {@link #retry()}. */
	public void start () {
		synchronized (runLock) {
			stop();
			if (logger.traceEnabled) logger.trace(category, "Started retry thread: " + name);
			delayIndex = 0;
			running = true;
			retryThread = new Thread(name) {
				public void run () {
					try {
						initialize();
						while (running) {
							retry();
							try {
								Thread.sleep(150L);
							}catch (Exception e) {}
						}
					} catch (Throwable ex) {
						throw new RuntimeException("Retry error: " + name, ex);
					} finally {
						synchronized (runLock) {
							fRetry.this.stop();
							if (logger.traceEnabled) logger.trace(category, "Stopped retry thread: " + name);
							stopped();
							retryThread = null;
							runLock.notifyAll();
						}
					}
				}
			};
			retryThread.start();
		}
	}

	/** Interrupts the retry thread and waits for it to terminate. */
	public void stop () {
		synchronized (runLock) {
			if (!running) return;
			running = false;
			Thread retryThread = this.retryThread;
			if (retryThread == Thread.currentThread()) return;
			if (logger.traceEnabled) logger.trace(category, "Waiting for retry thread to stop: " + name);
			retryThread.interrupt();
			stopped();
			while (this.retryThread == retryThread) {
				try {
					runLock.wait();
				} catch (InterruptedException ex) {
				}
			}
		}
	}

	/** Called once after {@link #start()}, on the retry thread. */
	protected void initialize () {
	}

	/** Called repeatedly on the retry thread between {@link #start()} and {@link #stop()}. If a runtime exception is thrown, the
	 * retry thread is stopped. {@link #success()} or {@link #failed()} should be called. */
	abstract protected void retry ();

	/** Called when the retry thread has been stopped. Called on the thread calling {@link #stop()} or on the retry thread if an
	 * exception occurred. */
	protected void stopped () {
	}

	/** Subclasses should call this from {@link #retry()} to indicate success, resets the next failure sleep time. */
	protected void success () {
		delayIndex = 0;
	}

	/** Subclasses should call this from {@link #retry()} to indicate failure, sleeps for some time. */
	protected void failed () {
		if (retryDelays[delayIndex] == 0) throw new RuntimeException("Retry thread failed: " + name);
		try {
			Thread.sleep(retryDelays[delayIndex]);
		} catch (InterruptedException ignored) {
		}
		delayIndex++;
		if (delayIndex == retryDelays.length) delayIndex = 0;
	}

	/** The delays to use for repeated failures. If more failures occur than entries, the last entry is used. If a delay is zero,
	 * the retry thread is stopped by throwing an exception. */
	public void setRetryDelays (int... retryDelays) {
		this.retryDelays = retryDelays;
	}
}
